package top.v5it.japi.ddd.core.web;

import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.web.client.HttpClientErrorException;
import top.v5it.japi.ddd.core.exception.DaoException;
import top.v5it.japi.ddd.core.exception.NotFoundException;
import top.v5it.japi.ddd.core.util.BeanUtils;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;

/**
 * @author zhanpu
 * @date 2023/3/28
 */
@Slf4j
public abstract class BaseController {

    static final String SERVICE_NAME_SUFFIX = "Service";

    /**
     * get bean in the spring context by name.
     *
     * @param name the bean name
     * @return the instance of the bean
     */
    protected Object getBean(String name) {
        final String serviceName =
                StrUtil.format("{}{}", StrUtil.toCamelCase(name, '-'), SERVICE_NAME_SUFFIX);
        try {
            return SpringUtil.getBean(serviceName);
        } catch (NoSuchBeanDefinitionException e) {
            throw new NotFoundException();
        }
    }

    /**
     * 将{@link HttpServletRequest}请求参数转换成{@link Map}
     *
     * @param json
     * @param request
     * @return
     */
    protected Map<String, Object> toMap(Map<String, Object> json, HttpServletRequest request) {
        json = Optional.ofNullable(json).orElseGet(HashMap::new);
        for (Enumeration<String> e = request.getParameterNames(); e.hasMoreElements(); ) {
            String key = e.nextElement();
            String value = request.getParameter(key);
            json.put(key, value);
        }
        return json;
    }

    /**
     * get the method of the service by name, using reflect.
     *
     * @param service
     * @param name    the name of the method
     * @return the reference of the method
     */
    protected Method getMethod(Object service, String name) {
        Assert.notBlank(name, "The method name is empty!");
        Method[] allOfMethods = service.getClass().getDeclaredMethods();
        Method rtn = null;
        for (Method method : allOfMethods) {
            if (method.getName().equals(name)) rtn = method;
        }
        //if have override, return the last one.
        if (rtn != null) return rtn;
        log.error("No such method [{}] in the service [{}]", name, service.getClass().getName());
        throw new NotFoundException();
    }

    /**
     * get the arguments that invoke the method need.
     * The first thing is put the value object to the first argument, if it's available.
     * Then put the other arguments in order.
     *
     * @param method
     * @param json
     * @param vo
     * @return the arguments
     */
    protected Object[] getArguments(Method method, Map<String, Object> json, Object vo) {
        int length = method.getParameterCount();
        if (length == 0) return null;
        int index = 0;
        List<Object> args = new ArrayList<>();

        //add the value object to the first argument, if it's available.
        if (vo != null) {
            args.add(vo);
            index++;
        }

        //add the other arguments in order.
        Parameter[] allOfParameters = method.getParameters();
        for (; index < length; index++) {
            Parameter parameter = allOfParameters[index];
            String name = parameter.getName();
            Object value = BeanUtils.bind(parameter.getParameterizedType(), json.get(name));
            args.add(value);
        }
        return args.toArray();
    }

    /**
     * 验证vo对象字段合法性
     *
     * @param vo
     */
    protected void validateEntity(Object vo) {
        Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
        Set<ConstraintViolation<Object>> set = validator.validate(vo);
        for (ConstraintViolation<Object> constraintViolation : set) {
            throw new IllegalArgumentException(constraintViolation.getMessage());
        }
    }

    /**
     * invoke the method of the service using reflect.
     * if the value object is available, it must be the first argument.
     *
     * @param service the service
     * @param method  the method
     * @param args    the other arguments
     * @return the result after invoking.
     */
    @SuppressWarnings({"unchecked"})
    protected Object invoke(Object service, Method method, Object[] args) {
        try {
            return ArrayUtil.isEmpty(args)
                    ? method.invoke(service)
                    : method.invoke(service, args);
        } catch (IllegalAccessException | InvocationTargetException e) {
            // 校验参数可能出现的异常
            if (ExceptionUtil.isCausedBy(e, IllegalArgumentException.class)) {
                throw new IllegalArgumentException(ExceptionUtil.getCausedBy(e, IllegalArgumentException.class).getMessage());
            }
            // 找不到远程实例
            if (ExceptionUtil.isCausedBy(e, IllegalStateException.class)) {
                throw new IllegalArgumentException(ExceptionUtil.getCausedBy(e, IllegalStateException.class).getMessage());
            }
            // 调用接口可能出现的异常
            if (ExceptionUtil.isCausedBy(e, HttpClientErrorException.class)) {
                log.error(e.getMessage(), e);
                final String message = ExceptionUtil.getCausedBy(e, HttpClientErrorException.class).getMessage();
                // 移除前缀和后缀多余字符，只保留 JSON 数据部分
                String json = message.substring(message.indexOf("{"), message.lastIndexOf("}") + 1);
                // 替换<EOL>标记为空字符串
                json = json.replace("<EOL>", "");
                final JSONObject jsonObject = JSONUtil.parseObj(json);
                throw new RuntimeException(StrUtil.format("{}[{}]", jsonObject.getStr("message"), jsonObject.getStr("path")));
            }
            // 操作数据库可能出现的异常
            if (ExceptionUtil.isCausedBy(e, DaoException.class)) {
                throw new RuntimeException(ExceptionUtil.getCausedBy(e, DaoException.class).getMessage());
            }
            throw new RuntimeException("error when invoking the service by reflect", e);
        }
    }
}
